#include <cassert>
#include <cstdlib>
#include <iostream>
#include <zlib.h>
#include <cstring>
#include "uncompress.h"

void logUncompressError(int error)
{
	switch (error)
	{
		case Z_MEM_ERROR:
			std::cerr << "Out of memory while (de)compressing data!" << std::endl ;
			break;
		case Z_VERSION_ERROR:
			std::cerr << "Incompatible zlib version!" << std::endl ;
			break;
		case Z_NEED_DICT:
		case Z_DATA_ERROR:
			std::cerr << "Incorrect zlib compressed data!" << std::endl ;
			break;
		default:
			std::cerr << "Unknown error while (de)compressing data!" << std::endl ;
	}
}


char* decompress(const char* data, int dataSize, int expectedSize)
{
	char* out;
	out = (char*) malloc(expectedSize) ;
	assert(out != NULL) ;

	z_stream strm;
	
	strm.zalloc = Z_NULL;
	strm.zfree = Z_NULL;
	strm.opaque = Z_NULL;
	strm.next_in = (Bytef *) data ;
	strm.avail_in = dataSize ;
	strm.next_out = (Bytef *) out ;
	strm.avail_out = expectedSize ;
	
	int ret = inflateInit2(&strm, 15 + 32);
	
	if (ret != Z_OK) {
		logUncompressError(ret);
		return NULL ;
	}
	
	do {
		ret = inflate(&strm, Z_SYNC_FLUSH);
		
		switch (ret) {
			case Z_NEED_DICT:
			case Z_STREAM_ERROR:
				ret = Z_DATA_ERROR;
			case Z_DATA_ERROR:
			case Z_MEM_ERROR:
				inflateEnd(&strm);
				logUncompressError(ret);
				return NULL ;
		}
		
		if (ret != Z_STREAM_END) {
			int oldSize = expectedSize ;
			out = (char*) realloc(out, expectedSize * 2) ;
			expectedSize *= 2 ;
			assert(out != NULL) ;
			
			strm.next_out = (Bytef *)(out + oldSize);
			strm.avail_out = oldSize;
		}
	}
	while (ret != Z_STREAM_END);
	
	if (strm.avail_in != 0) {
		logUncompressError(Z_DATA_ERROR);
		return NULL ;
	}
	
	const int outLength = expectedSize - strm.avail_out;
	inflateEnd(&strm);
	
	char *realOut = (char*) malloc(outLength) ;
	assert(realOut != NULL) ;
	for(int i = 0 ;i < outLength ; i++)
	{
		realOut[i] = out[i] ;
	}

	return realOut;
}